
"""

A simple class to query seeqpod via it's public API.

It searches just for music, tries to prune dead links, and returns python
dicts instead of some fugly XSPF.

"""

import urllib2, httplib, urlparse, socket
import sys, os
import difflib
import tempfile, shutil
import operator

from xml.sax import make_parser, handler, SAXParseException
from threading import Thread
from Queue import Queue


# Threaded method.
def update_track_from_loc():
    track = track_queue.get()
    #track = q.get()

    url = track.get('url', None)
    #print "[%s]...starting." % url

    if not url: return 

    # Turn url into a host/request.
    o = urlparse.urlsplit(url)
    host = o.hostname
    port = o.port
    path = o.path + o.query

    
    track['length'] = ""
    track['type'] = ""
    track['date'] = ""

    try:
        c = httplib.HTTPConnection(host, port)
        c.request("HEAD", path)

        resp = c.getresponse()
        h = dict(resp.getheaders())

        length = h.get('content-length', 0)
        type = h.get('content-type', "")
        date = h.get('last-modified', "")
        
        track['length'] = int(length)
        track['type'] = type
        track['date'] = date

    # If anything fails, we'll clean it up later.
    except httplib.HTTPException, e:
        pass
    except socket.error, e:
        pass
    except socket.timeout, e:
        pass

    track_queue.task_done()

        



class XSPFParser(handler.ContentHandler):
    def __init__(self):
        self.urls = []
        self.path = ""
        self.tracks = []
        self.cur_track = None

    def parseFile(self, filename):
        try:
            parser = make_parser()
            parser.setContentHandler(self)
            parser.parse(filename)
            return True
        except SAXParseException:
            return False
    
    def startElement(self, name, attrs):
        self.path += "/%s" % name
        self.content = ""

        if name == "track":
            self.cur_track = {}

    def characters(self, content):
        self.content += content

    def endElement(self, name):
        if self.path == "/playlist/trackList/track/title":
            self.cur_track['title'] = self.content
        elif self.path == "/playlist/trackList/track/creator":
            self.cur_track['artist'] = self.content
        elif self.path == "/playlist/trackList/track/location":
            self.cur_track['url'] = self.content
        elif self.path == "/playlist/trackList/track/genre":
            self.cur_track['genre'] = self.content
        elif self.path == "/playlist/trackList/track/album":
            self.cur_track['album'] = self.content

        if name == "track":
            self.tracks.append(self.cur_track)

        
        # rewind path after the element closes.
        off = self.path.rfind("/")
        if off >= 0:
            self.path = self.path[0:off]

    
    def get_tracks(self):
        return self.tracks


track_queue = Queue()

class Seeqpod():
    
    host_api = "http://www.seeqpod.com/api/v0.2/%(uid)s/music/search/%(q)s/%(start)s/%(num)s/"
   
    def __init__(self, uid):
        if uid is None: return False
        self.uid = uid

        self.parser = XSPFParser()

    def find(self, q, start=0, num=50):

        tracks = []

        # Some of them don't have proper content-types. ;)
        audio_tracks = []

        q_enc = urllib2.quote(q)
        qry = self.host_api % ({'uid': self.uid, 'q': q_enc, 'start': start, 'num': num})

        self.parser.parseFile(qry)
        self.tracks = self.parser.get_tracks()

        if len(self.tracks) == 0:
            return None

        #print "len(self.tracks): %d" % len(self.tracks)
        # We should go through and check each file, add in a size if we can.

        for t in self.tracks:
            track_queue.put(t)
        
        # I'm not that patient.
        socket.setdefaulttimeout(10)

        # start as many threads as `num`. Why not.
        for i in range(num):
            t = Thread(target=update_track_from_loc)
            t.setDaemon(True)
            t.start()
            
        
        # Wait..
        track_queue.join()

        for t in self.tracks:
            if t['type'] == 'audio/mpeg' and t['length'] > 0: 
                audio_tracks.append(t)

        tracks = audio_tracks

        
        # Now, filter though and give back tracks which are "mostly named like"
        # what you asked for.
        
        # Note: This is a pretty hackish heuristic.
         
        differ = difflib.SequenceMatcher()
        for t in self.tracks:
            differ.set_seqs(q, "%s %s" % (t['artist'], t['title']))
            t['match'] = differ.quick_ratio()


        best_tracks = []
        for t in sorted(tracks, key=operator.itemgetter('match'), reverse=True):
            if t['match'] > 0.8:
                best_tracks.append(t)

        best_tracks.sort(key=operator.itemgetter('length'), reverse=True) 
        
        return best_tracks


    def get_track(self, track, name=None):
        try:
            p = tempfile.NamedTemporaryFile() 
            o = urlparse.urlsplit(track['url']) 
            c = httplib.HTTPConnection(o.hostname, o.port)
            c.request("GET", o.path + o.query)
            
            resp = c.getresponse()
            data = resp.read(4096)

            while data:
                p.write(data)
                data = resp.read(4096)
            
            c.close()
            
        except Exception, e:
            p.close()
            c.close()
            print "EX: %s" % str(e)
            return None


        try:
            tmpdir = tempfile.gettempdir()

            if name is None:
                out_path = "%s/%s_%s.mp3" % (tmpdir, track['artist'], track['title'])
            else:
                out_path = "%s/%s" % (tmpdir, name)

            shutil.copy(p.name, out_path)
            p.close()
        except Exception, e:
            print "EX: %s" % str(e)
        
        
        return out_path
